package com.allwees.bs.c.module.payment.controller;

import cn.hutool.core.io.FileUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.allwees.bs.c.module.base.controller.BaseController;
import com.allwees.bs.c.module.order.context.event.OrderRefundFailedEvent;
import com.allwees.bs.c.module.order.context.event.OrderRefundSuccessEvent;
import com.allwees.bs.c.module.order.dto.OrderDTO;
import com.allwees.bs.c.module.order.entity.OrderItemEntity;
import com.allwees.bs.c.module.order.service.CartService;
import com.allwees.bs.c.module.order.service.IOrderItemService;
import com.allwees.bs.c.module.payment.entity.PaymentLogEntity;
import com.allwees.bs.c.module.payment.req.OceanpayCallback;
import com.allwees.bs.c.module.payment.req.PaymentCaptureReq;
import com.allwees.bs.c.module.payment.service.IPaymentService;
import com.allwees.bs.c.module.payment.service.OceanpayService;
import com.allwees.bs.c.module.payment.service.impl.PaybyServiceImpl;
import com.allwees.bs.core.model.order.wf.order.EOrderItemState;
import com.allwees.core.common.util.http.RequestContext;
import com.allwees.core.common.util.json.JsonUtil;
import com.allwees.support.payment.constant.PayStatusEnum;
import com.allwees.support.payment.constant.RefundStatusEnum;
import com.allwees.support.payment.model.webhook.PaypalWebhookModel;
import com.allwees.support.payment.payby.configuration.PaybyProperties;
import com.allwees.support.payment.paypal.constant.EPaypalWebHookEventType;
import com.payby.gateway.openapi.model.AcquireOrder;
import com.payby.gateway.openapi.model.RefundOrder;
import com.payby.gateway.openapi.response.GetPlaceOrderResponse;
import com.payby.gateway.openapi.response.PlaceRefundOrderResponse;
import com.payby.gateway.sdk.misc.util.RsaUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.junit.Assert;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.nio.charset.Charset;

/**
 * Pay webhook
 */
@Slf4j
@RestController
@RequestMapping("/api/webhook")
public class WebhookController extends BaseController {

    @Autowired
    private IPaymentService paymentService;

    @Autowired
    private OceanpayService oceanpayService;

    @Autowired
    private CartService cartService;
    @Autowired
    private PaybyServiceImpl paybyService;
    @Autowired
    private PaybyProperties paybyProperties;
    @Autowired
    private ApplicationContext applicationContext;
    @Autowired
    private IOrderItemService orderItemService;

    /**
     * 接收回调信息并处理
     *
     * @param body
     * @param request
     * @return
     */
    @PostMapping("/process")
    public Object processHook(@RequestBody String body, HttpServletRequest request) {
        log.info("body:{}", body);

        PaypalWebhookModel model = JsonUtil.fromJson(body, PaypalWebhookModel.class);
        EPaypalWebHookEventType eventType = EPaypalWebHookEventType.getByValue(model.getEvent_type());
        if (eventType == null) {
            throw new RuntimeException("Unknown Event Type:" + model.getEvent_type());
        }
        String orderUuid = "";
        String channelUuid = RequestContext.getChannel();

        if (EPaypalWebHookEventType.CAPTURE.equals(eventType)) {
            orderUuid = model.getResource().getCustom_id();
            paymentService.paypalCaptureWebHooks(orderUuid, model.getResource().getId(), channelUuid);
        } else if (EPaypalWebHookEventType.REFUNDED.equals(eventType)) {
            orderUuid = model.getResource().getCustom_id();
            paymentService.paypalRefundedWebHooks(orderUuid, model.getResource().getId(), channelUuid);
        } else if (EPaypalWebHookEventType.CHECKOUT_APPROVED.equals(eventType)) {
            orderUuid = model.getResource().getPurchase_units().get(0).getCustom_id();
            paymentService.paypalCaptureWebHooks(orderUuid, model.getResource().getId(), channelUuid);
        }
        return orderUuid;
    }

    /**
     * 1.支付结果回调；
     * 2.退款结果回调；
     * 3.其他业务状态推送；
     * 当一笔交易出现拒付，调单或申诉时，Oceanpayment 会把这笔交易的业务状态实时通过 noticeUrl 字段通知到网站
     *
     * @param callback
     * @return
     */
    @PostMapping(value = "/oceanpay", consumes = "application/xml")
    public String oceanPayCallback(@RequestBody OceanpayCallback callback) {
        log.info("===>oceanpay callback:{}", callback);
        if (callback.isPaymentSuccess()) {
            if (oceanpayService.paySuccess(callback)) {
                return "receive-ok";
            }
        }

        if (callback.isRefundSuccess()) {
            //ongo 退款回调
        }
        return "not-ok";
    }

    /**
     * 接收付款结果回调信息并处理
     *
     * @param req
     * @param httpResponse
     * @return
     */
    @ResponseBody
    @PostMapping("/payby/payResult")
    public void processPayedHook(HttpServletRequest req, HttpServletResponse httpResponse) {
        try {
            String sign = req.getHeader("sign");
            String body = IOUtils.toString(req.getInputStream(), "UTF-8");
            log.info("processPayedHook body:{}", body);
            // setting payby publicKey path
            String payByPubKey = FileUtil.readUtf8String(paybyProperties.getPayby_public_key_path());
            // assert verify sign
            Assert.assertTrue(RsaUtil.verifySign(body, Charset.forName("UTF-8"), sign, payByPubKey));
            GetPlaceOrderResponse placeOrderResponse = JSON.parseObject(body, new TypeReference<GetPlaceOrderResponse>() {
            });

            AcquireOrder acquireOrder = placeOrderResponse.getAcquireOrder();

            PaymentLogEntity paymentLogEntity = paybyService.queryPayLog(acquireOrder.getMerchantOrderNo());
            if (paymentLogEntity.getStatus() == 2) {
                log.warn("repeated acquireOrder callback body=>" + placeOrderResponse);
                return;
            }

            // 支付成功
            if (StringUtils.equals(acquireOrder.getStatus(), PayStatusEnum.PAID_SUCCESS.name()) ||
                    StringUtils.equals(acquireOrder.getStatus(), PayStatusEnum.SETTLED.name())) {
                PaymentCaptureReq paymentCaptureReq = new PaymentCaptureReq();
                paymentCaptureReq.setOrderUuid(acquireOrder.getMerchantOrderNo());
                paymentCaptureReq.setToken(acquireOrder.getOrderNo());
//                paymentCaptureReq.setPayerId(acquireOrder.getPaymentInfo().get);
                // mq
//                MqUtil.send(new PaypalCaptureEvent(paymentCaptureReq));
                cartService.clearUserCart(getUserUuid());
                OrderDTO order = paybyService.orderPayed(paymentCaptureReq);
            } else {
                log.error("MerchantOrderNo:{}支付失败", acquireOrder.getMerchantOrderNo());
            }

            log.info("acquireOrder callback body=>" + placeOrderResponse);
        } catch (Exception e) {
            log.error("processPayedHook exception", e);
        } finally {
            try {
                httpResponse.setContentType("application/json;charset=UTF-8");
                httpResponse.getOutputStream().write("SUCCESS".getBytes("UTF-8"));
                httpResponse.flushBuffer();
            } catch (Exception ex) {
                log.error("processPayedHook response exception", ex);
            }
        }
    }

    /**
     * 接收退款结果并处理
     *
     * @param req
     * @param httpResponse
     * @return
     */
    @ResponseBody
    @PostMapping("/payby/refundResult")
    public void processRefundedHook(HttpServletRequest req, HttpServletResponse httpResponse) {
        try {
            String sign = req.getHeader("sign");
            String body = IOUtils.toString(req.getInputStream(), "UTF-8");
            log.info("processPayedHook body:{}", body);
            // setting payby publicKey path
            String payByPubKey = FileUtil.readUtf8String(paybyProperties.getPayby_public_key_path());

            // assert verify sign
            Assert.assertTrue(RsaUtil.verifySign(body, Charset.forName("UTF-8"), sign, payByPubKey));

            // Business logic
            PlaceRefundOrderResponse refundOrderResponse = JSON.parseObject(body, new TypeReference<PlaceRefundOrderResponse>() {
            });

            RefundOrder refundOrder = refundOrderResponse.getRefundOrder();
            String orderItemUuid = refundOrder.getReserved();
            if (StringUtils.isBlank(orderItemUuid)) {
                log.error("empty order item, OriginMerchantOrderNo:{}=>", refundOrder.getOriginMerchantOrderNo());
                return;
            }

            OrderItemEntity orderItemEntity = orderItemService.findByOrderItemUuid(orderItemUuid);
            if (orderItemEntity == null) {
                log.error("not existed order item:{}=>", orderItemUuid);
                return;
            }
            if (orderItemEntity.getState() == EOrderItemState.WITH_WAIT_SHIP_CASH_REFUNDED
                    || orderItemEntity.getState() == EOrderItemState.WITH_RECEIVED_CASH_REFUNDED) {
                log.warn("repeated refundOrder callback body=>" + refundOrderResponse);
                return;
            }

            // 退款成功
            if (StringUtils.equals(refundOrder.getStatus(), RefundStatusEnum.REFUND_SETTLED.name()) ||
                    StringUtils.equals(refundOrder.getStatus(), RefundStatusEnum.SUCCESS.name())) {
                paybyService.paybyRefundedWebHooks(orderItemUuid);
                applicationContext.publishEvent(new OrderRefundSuccessEvent(orderItemUuid));
                log.error("MerchantOrderNo:{},item:{}退款成功", refundOrder.getOriginMerchantOrderNo(), orderItemUuid);
            } else {
                applicationContext.publishEvent(new OrderRefundFailedEvent(orderItemUuid));
                log.error("MerchantOrderNo:{},item:{}退款失败", refundOrder.getOriginMerchantOrderNo(), orderItemUuid);
            }

            log.info("refundOrder callback body=>" + refundOrderResponse);
        } catch (Exception e) {
            log.error("processRefundedHook exception", e);
        } finally {
            try {
                httpResponse.setContentType("application/json;charset=UTF-8");
                httpResponse.getOutputStream().write("SUCCESS".getBytes("UTF-8"));
                httpResponse.flushBuffer();
            } catch (Exception ex) {
                log.error("processRefundedHook response exception", ex);
            }
        }
    }
}